package org.zergle.lex;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import org.zergle.utils.CharStack;

/**
 * 抽象的词法分析器，具体的词法分析过程由子类实现
 * 
 * @author Johnson.Lee
 * 
 */
public abstract class AbstractLexer {
	/**
	 * 字符流缓冲区
	 */
	protected static int BUFF_SIZE = 255;
	
	/**
	 * 当前字符在流中的位置
	 */
	protected long pos;

	/**
	 * 当前行号
	 */
	protected int line;

	/**
	 * 当前列号
	 */
	protected int column;

	/**
	 * 是否回车
	 */
	protected boolean isCR;

	/**
	 * 是否换行
	 */
	protected boolean isLF;

	/**
	 * 字符堆栈
	 */
	protected CharStack stack;

	/**
	 * 字符流
	 */
	protected PushbackReader reader;

	/**
	 * 当前<code>Token</code>
	 */
	protected Token currToken;

	/**
	 * 基于字符构造词法分析器
	 * 
	 * @param reader
	 *            字符流
	 */
	public AbstractLexer(Reader reader) {
		this.line = 1;
		this.column = 1;
		this.stack = new CharStack();
		this.reader = new PushbackReader(new BufferedReader(reader), BUFF_SIZE);
	}

	/**
	 * 基于字节输入流构造词法分析器
	 * 
	 * @param is
	 *            字节输入流
	 */
	public AbstractLexer(InputStream is) {
		this(new InputStreamReader(is));
	}

	/**
	 * 基于字节输入流和指定的字符集构造词法分析器
	 * 
	 * @param is
	 *            字节输入流
	 * @param charsetName
	 *            字符集名称
	 * @throws UnsupportedEncodingException
	 *             当指定字符集不支持时，抛出此异常
	 */
	public AbstractLexer(InputStream is, String charsetName)
			throws UnsupportedEncodingException {
		this(new InputStreamReader(is, charsetName));
	}

	/**
	 * 基于字节输入流和指定的字符集构造词法分析器
	 * 
	 * @param is
	 *            字节输入流
	 * @param charset
	 *            字符集
	 */
	public AbstractLexer(InputStream is, Charset charset) {
		this(new InputStreamReader(is, charset));
	}

	/**
	 * 测试是否还有可分析的{@link Token}
	 * 
	 * @return 如果当前{@link Token}后还有{@link Token}，则返回true，否则返回false
	 */
	public boolean hasNext() {
		try {
			this.skipWhite();
		} catch (IOException e) {
			return false;
		}
		return true;
	}
	
	/**
	 * 从字符输入流中读取字符并分析，返回下一个{@link Token}。当分析到输入流结束时，子类必须关闭字符输入流
	 * 
	 * @return 下一个{@link Token}
	 */
	public abstract Token next() throws IOException;
	

	/**
	 * 读取字符流中下一个字符
	 * 
	 * @return 读取到的字符
	 * @throws IOException
	 *             当读到输入流末尾时，抛出<code>java.io.EOFException</code>
	 */
	protected char read() throws IOException {
		int c = -1;
		if (this.reader == null || (c = this.reader.read()) == -1) {
			throw new EOFException();
		}
		switch (c) {
		case '\r':
		case '\n':
			isLF = true;
			isCR = (c == '\r');
			column = 1;
			if (isCR && isLF)
				break;
			++line;
			break;
		default:
			++column;
			isCR = false;
			isLF = false;
		}
		pos ++;
		return (char) c;
	}

	/**
	 * 将指定字符退回字符流中
	 * 
	 * @param c
	 *            字符
	 * @throws IOException
	 */
	protected void unread(char c) throws IOException {
		this.reader.unread(c);
		switch (c) {
		case '\r':
		case '\n':
			column = 1;
			break;
		default:
			column = column < 1 ? 1 : column - 1;
		}
		this.pos --;
	}

	/**
	 * 将队列中的字符转存为字符串，并清空队列
	 * 
	 * @return 返回字符队列的字符串形式
	 */
	protected String dumpStack() {
		String image = this.stack.toString();
		this.stack.clear();
		return image;
	}

	/**
	 * 跳过缓冲区的空白字符
	 * 
	 * @throws IOException
	 */
	protected void skipWhite() throws IOException {
		int c = this.read();
		if (c == -1)
			return;
		while (Character.isWhitespace(c)) {
			c = this.read();
			if (c == -1)
				return;
		}
		this.unread((char) c);
	}

	/**
	 * 根据指定的类型创建{@link Token}
	 * 
	 * @param type
	 *            {@link Token}的类型
	 * @return 创建的{@link Token}
	 */
	protected Token createToken(Enum<?> type) {
		String image = this.dumpStack();
		Token token = new Token(line, column - image.length(), type, image);
		token.previous = this.currToken;
		token.position = this.pos;
		if (this.currToken != null) {
			this.currToken.next = token;
		}
		this.currToken = token;
		return token;
	}

}
